#include "./WizVFSBlockStorage.hpp"
#include "./WizVFSBlockStorageParam.hpp"
#include "../../../Include/Base/WizBaseException.hpp"

namespace Wiz
{
    namespace VFS
    {
        namespace BlockStorage
        {
            //**********************************************//
            //                                              //
            //              BlockStorageInfo                //
            //                                              //
            //**********************************************//
            //**********************************************//
            //                                              //
            //                 ChunkInfo                    //
            //                                              //
            //**********************************************//
            //                 BlockInfo                    //
            //----------------------------------------------//
            //                   Block                      //
            //==============================================//
            //~~                                          ~~//
            //==============================================//
            //                 BlockInfo                    //
            //----------------------------------------------//
            //                   Block                      //
            //==============================================//
            //~~                                          ~~//
            //~~    {BlockStorageInfo::m_ChunkCount}      ~~//
            //~~                                          ~~//
            //**********************************************//
            //                                              //
            //                 ChunkInfo                    //
            //                                              //
            //**********************************************//
            //                 BlockInfo                    //
            //----------------------------------------------//
            //                   Block                      //
            //==============================================//
            //~~ {BlockStorageInfo::m_BlockCountPerChunk} ~~//
            //==============================================//
            //                 BlockInfo                    //
            //----------------------------------------------//
            //                   Block                      //
            //**********************************************//
            //~~                                          ~~//
            //~~                                          ~~//
            //**********************************************//

            //////////////////////////////////////////////////////////////////////////
            Type::Type()
                : m_AccessorPtr(WIZ_NULLPTR)
                , m_FilePtr(WIZ_NULLPTR)
                , m_BlockSizeComplete(0)
                , m_ChunkSize(0)
                , m_ChunkSizeComplete(0)
            {
                m_Endian = ::Wiz::Endian::Native::Test();
            }

            Type::Type(::Wiz::VFS::Info::BlockStorage::In InInfo)
                : m_AccessorPtr(WIZ_NULLPTR)
                , m_FilePtr(WIZ_NULLPTR)
                , m_Endian(InInfo.m_Endian)
            {
                m_Info.m_BlockSize          = InInfo.m_BlockSize;
                m_Info.m_BlockCountPerChunk = InInfo.m_BlockCountPerChunk;

                m_BlockSizeComplete = m_Info.m_BlockSize + sizeof(::Wiz::VFS::BlockStorage::BlockInfo::Type);
                m_ChunkSize         = m_BlockSizeComplete * m_Info.m_BlockCountPerChunk;
                m_ChunkSizeComplete = m_ChunkSize + sizeof(::Wiz::VFS::BlockStorage::ChunkInfo::Type);
            }

            //////////////////////////////////////////////////////////////////////////

            ::Wiz::Bool::Type Type::Open(::Wiz::String::In InStr)
            {
                if (::Wiz::IsValidPtr(m_AccessorPtr))
                {
                    this->Close();
                }
                
                if (::Wiz::File::Exist(InStr))
                {
                    return this->Load(InStr);
                }

                return this->Create(InStr);
            }

            ::Wiz::Bool::Type Type::Create(::Wiz::String::In InStr)
            {
                m_FilePtr = ::Wiz::File::Open(InStr);

                m_AccessorPtr = ::Wiz::Accessor::Create(m_FilePtr);
                if (::Wiz::NotValidPtr(m_AccessorPtr))
                {
                    return ::Wiz::Bool::False;
                }

                if (m_AccessorPtr->SwitchEndian(m_Endian))
                {
                    m_AccessorPtr->Expand(sizeof(::Wiz::VFS::BlockStorage::StorageInfo::Type));
                    ExpandByAppendChunk();
                    m_Info.m_ChunkCount = 1;
                    WriteInfoAndAdvance();
                    return ::Wiz::Bool::True;
                }

                return ::Wiz::Bool::False;
            }

            ::Wiz::Bool::Type Type::Load(::Wiz::String::In InStr)
            {
                m_FilePtr = ::Wiz::File::Open(InStr, ::Wiz::File::OpenOptions::eOpenRead | ::Wiz::File::OpenOptions::eOpenWrite);

                m_AccessorPtr = ::Wiz::Accessor::Create(m_FilePtr);
                if (::Wiz::NotValidPtr(m_AccessorPtr))
                {
                    return ::Wiz::Bool::False;
                }

                ReadInfoAndAdvance();
                m_BlockSizeComplete = m_Info.m_BlockSize + sizeof(::Wiz::VFS::BlockStorage::BlockInfo::Type);
                m_ChunkSize         = m_BlockSizeComplete * m_Info.m_BlockCountPerChunk;
                m_ChunkSizeComplete = m_ChunkSize + sizeof(::Wiz::VFS::BlockStorage::ChunkInfo::Type);

                if (m_AccessorPtr->SwitchEndian(m_Endian))
                {
                    return ReadDirTree();
                }

                return ::Wiz::Bool::False;
            }

            ::Wiz::Void::Type Type::Close()
            {
                WriteDirTree();

                if (::Wiz::IsValidPtr(m_AccessorPtr))
                {
                    ::Wiz::Accessor::Destory(m_AccessorPtr);
                    m_AccessorPtr = WIZ_NULLPTR;
                }
                if (::Wiz::IsValidPtr(m_FilePtr))
                {
                    ::Wiz::File::Close(m_FilePtr);
                    m_FilePtr = WIZ_NULLPTR;
                }
            }

            //////////////////////////////////////////////////////////////////////////

            ::Wiz::Bool::Type Type::Read(::Wiz::ByteBuffer::Out OutBuff, ::Wiz::String::In InFile)
            {
                ::Wiz::UTF8::String::Type FilePath(InFile);
                ::Wiz::VFS::BlockStorage::DirTree::Ptr DTPtr = m_DirTree.FindFile(FilePath);
                if (::Wiz::NotValidPtr(DTPtr))
                {
                    return ::Wiz::Bool::False;
                }
                ::Wiz::VFS::BlockStorage::Param::Type Param;
                Param.m_BlockInfo.m_PreChunkID = DTPtr->m_ChunkID;
                Param.m_BlockInfo.m_PreBlockID = DTPtr->m_BlockID;
                OutBuff.Resize(DTPtr->m_Size);

                return this->Read(OutBuff.GetPtr(), DTPtr->m_Size, Param);
            }
            ::Wiz::Bool::Type Type::Write(::Wiz::ByteBuffer::In InBuff, ::Wiz::String::In InStr)
            {
                ::Wiz::UTF8::String::Type FilePath(InStr);
                ::Wiz::VFS::BlockStorage::DirTree::Ptr DTPtr = WIZ_NULLPTR;
                if (m_DirTree.InsertFile(FilePath, &DTPtr) && ::Wiz::IsValidPtr(DTPtr))
                {
                    ::Wiz::VFS::BlockStorage::Param::Type Param;
                    if (this->Write(InBuff.GetPtr(), (::Wiz::Size::Type)InBuff.Size(), Param))
                    {
                        DTPtr->m_ChunkID = Param.m_CurrChunkID;
                        DTPtr->m_BlockID = Param.m_LastWriteBlockID;
                        DTPtr->m_Size    = (::Wiz::Size::Type)InBuff.Size();

                        return ::Wiz::Bool::True;
                    }
                }
                return ::Wiz::Bool::False;
            }
            ::Wiz::Bool::Type Type::Replace(::Wiz::ByteBuffer::In InBuff, ::Wiz::String::In)
            {
                return ::Wiz::Bool::True;
            }
            ::Wiz::Bool::Type Type::Remove(::Wiz::String::In InFile)
            {
                ::Wiz::UTF8::String::Type FilePath(InFile);
                ::Wiz::VFS::BlockStorage::DirTree::Ptr DTPtr = m_DirTree.FindFile(FilePath);
                if (::Wiz::NotValidPtr(DTPtr))
                {
                    return ::Wiz::Bool::False;
                }

                ::Wiz::VFS::BlockStorage::Param::Type Param;
                Param.m_BlockInfo.m_PreChunkID = DTPtr->m_ChunkID;
                Param.m_BlockInfo.m_PreBlockID = DTPtr->m_BlockID;

                return this->Remove(Param);
            }

            ::Wiz::Bool::Type Type::Remove(::Wiz::VFS::BlockStorage::Param::InOut Param)
            {
                Param.m_Endian                  = m_Endian;
                Param.m_BlockSize               = m_Info.m_BlockSize;
                Param.m_BlockSizeComplete       = m_BlockSizeComplete;
                Param.m_ChunkSize               = m_ChunkSize;
                Param.m_ChunkSizeComplete       = m_ChunkSizeComplete;
                Param.m_AccessorPtr             = m_AccessorPtr;

                Param.m_BlockCountPerChunk      = m_Info.m_BlockCountPerChunk;

                Param.m_DataPtr                 = WIZ_NULLPTR;
                Param.m_DataSize                = 0;
                Param.m_DataCurrPos             = 0;

                Param.m_CurrChunkID             = Param.m_BlockInfo.m_PreBlockID;
                Param.m_LastWriteBlockID        = ::Wiz::UInt32::Max;

                while (Param.m_BlockInfo.m_PreChunkID == ::Wiz::UInt32::Max)
                {
                    ::Wiz::VFS::BlockStorage::Chunk::Type VirtualChunk;

                    VirtualChunk.SetPosition(
                        Param.m_BlockInfo.m_PreChunkID,
                        m_ChunkSizeComplete
                        );

                    VirtualChunk.Remove(Param);
                }

                return ::Wiz::Bool::True;
            }

            ::Wiz::Bool::Type Type::Read(::Wiz::Byte::OutPtr DataPtr, ::Wiz::Size::In DataSize, ::Wiz::VFS::BlockStorage::Param::InOut Param)
            {
                Param.m_Endian                  = m_Endian;
                Param.m_BlockSize               = m_Info.m_BlockSize;
                Param.m_BlockSizeComplete       = m_BlockSizeComplete;
                Param.m_ChunkSize               = m_ChunkSize;
                Param.m_ChunkSizeComplete       = m_ChunkSizeComplete;
                Param.m_AccessorPtr             = m_AccessorPtr;

                Param.m_BlockCountPerChunk      = m_Info.m_BlockCountPerChunk;

                Param.m_DataPtr                 = DataPtr;
                Param.m_DataSize                = DataSize;
                Param.m_DataCurrPos             = 0;

                ::Wiz::Int::Type BlockNum = DataSize / m_Info.m_BlockSize;
                if ((DataSize - (BlockNum * m_Info.m_BlockSize)) == 0)
                {
                    BlockNum--;
                }
                Param.m_DataCurrPos = BlockNum * m_Info.m_BlockSize;

                ::Wiz::VFS::BlockStorage::Block::Type VirtualBlock;
                for (; BlockNum >= 0; BlockNum--)
                {
                    VirtualBlock.SetPosition(
                        Param.m_BlockInfo.m_PreChunkID,
                        m_ChunkSizeComplete,
                        Param.m_BlockInfo.m_PreBlockID,
                        m_BlockSizeComplete
                        );
                    VirtualBlock.Read(Param);
                }

                return ::Wiz::Bool::True;
            }

            ::Wiz::Bool::Type Type::Write(::Wiz::Byte::InPtr DataPtr, ::Wiz::Size::In DataSize, ::Wiz::VFS::BlockStorage::Param::InOut Param)
            {
                Param.m_Endian                  = m_Endian;
                Param.m_BlockSize               = m_Info.m_BlockSize;
                Param.m_BlockSizeComplete       = m_BlockSizeComplete;
                Param.m_ChunkSize               = m_ChunkSize;
                Param.m_ChunkSizeComplete       = m_ChunkSizeComplete;
                Param.m_AccessorPtr             = m_AccessorPtr;

                Param.m_BlockCountPerChunk      = m_Info.m_BlockCountPerChunk;

                Param.m_DataPtr                 = ::Wiz::Cast::Const<::Wiz::Byte::Ptr>(DataPtr);
                Param.m_DataSize                = DataSize;
                Param.m_DataCurrPos             = 0;

                Param.m_BlockInfo.m_PreBlockID  = ::Wiz::UInt32::Max;
                Param.m_BlockInfo.m_PreChunkID  = ::Wiz::UInt32::Max;

                Param.m_CurrChunkID             = ::Wiz::UInt32::Max;
                Param.m_LastWriteBlockID        = ::Wiz::UInt32::Max;

                ::Wiz::VFS::BlockStorage::Chunk::Type VirtualChunk;
                ::Wiz::UInt::Type i;
                for (i = 0; i < m_Info.m_ChunkCount; i++)
                {
                    VirtualChunk.m_Position = sizeof(::Wiz::VFS::BlockStorage::StorageInfo::Type) + i * m_ChunkSizeComplete;
                    Param.m_CurrChunkID = i;
                    VirtualChunk.Write(Param);
                    if (Param.m_DataCurrPos == Param.m_DataSize)
                    {
                        break;
                    }
                }

                while (Param.m_DataCurrPos < Param.m_DataSize)
                {
                    if (this->ExpandByAppendChunk() == ::Wiz::Bool::False)
                    {
                        return ::Wiz::Bool::False;
                    }
                    VirtualChunk.SetPosition(m_Info.m_ChunkCount, m_ChunkSizeComplete);
                    Param.m_CurrChunkID = m_Info.m_ChunkCount;
                    VirtualChunk.Write(Param);
                    m_Info.m_ChunkCount++;
                }

                for (i = 0; i < Param.m_WriteBackChunkArray.Size(); i++)
                {
                    ::Wiz::VFS::BlockStorage::Chunk::Type VirtualChunk = Param.m_WriteBackChunkArray[i];
                    VirtualChunk.WriteInfoAndAdvance(Param);
                }

                for (i = 0; i < Param.m_WriteBackBlockArray.Size(); i++)
                {
                    ::Wiz::VFS::BlockStorage::Block::Type VirtualBlock = Param.m_WriteBackBlockArray[i];
                    VirtualBlock.WriteInfoAndAdvance(Param);
                }
                return ::Wiz::Bool::True;
            }

            ::Wiz::Bool::Type Type::ExpandByAppendChunk()
            {
                ::Wiz::Byte::Ptr ExpandPtr = ::Wiz::Cast::Static<::Wiz::Byte::Ptr>(::malloc(m_ChunkSizeComplete));
                if (::Wiz::NotValidPtr(ExpandPtr))
                {
                    throw ::Wiz::Exception::BadAlloc();
                    return ::Wiz::Bool::False;
                }

                memset(ExpandPtr, 0, m_ChunkSizeComplete);

                ::Wiz::VFS::BlockStorage::ChunkInfo::Ptr ChunkInfoPtr =
                    ::Wiz::Cast::Static<::Wiz::VFS::BlockStorage::ChunkInfo::Ptr>(ExpandPtr);
                if (m_Endian == ::Wiz::Endian::eLittle)
                {
                    ChunkInfoPtr->m_CountAvailable =
                        ::Wiz::Endian::FromNativeToLittleEndian(::Wiz::Cast::Static<::Wiz::UInt16::Type>(m_Info.m_BlockCountPerChunk));
                    ChunkInfoPtr->m_FirstAvailable =
                        ::Wiz::Endian::FromNativeToLittleEndian(::Wiz::Cast::Static<::Wiz::UInt16::Type>(0));
                }
                else if (m_Endian == ::Wiz::Endian::eBig)
                {
                    ChunkInfoPtr->m_CountAvailable =
                        ::Wiz::Endian::FromNativeToBigEndian(::Wiz::Cast::Static<::Wiz::UInt16::Type>(m_Info.m_BlockCountPerChunk));
                    ChunkInfoPtr->m_FirstAvailable =
                        ::Wiz::Endian::FromNativeToBigEndian(::Wiz::Cast::Static<::Wiz::UInt16::Type>(0));
                }
                
                ::Wiz::Byte::Ptr TempPtr = ExpandPtr + sizeof(::Wiz::VFS::BlockStorage::ChunkInfo::Type);
                for (::Wiz::Size32::Type i = 1; i < m_Info.m_BlockCountPerChunk; i++, TempPtr += m_BlockSizeComplete)
                {
                    ::Wiz::UInt32::Ptr BlockInfoPtr = ::Wiz::Cast::Static<::Wiz::UInt32::Ptr>(TempPtr);

                    if (m_Endian == ::Wiz::Endian::eLittle)
                    {
                        *BlockInfoPtr = ::Wiz::Endian::FromNativeToLittleEndian<::Wiz::UInt32::Type>(i);
                    }
                    else if (m_Endian == ::Wiz::Endian::eBig)
                    {
                        *BlockInfoPtr = ::Wiz::Endian::FromNativeToBigEndian<::Wiz::UInt32::Type>(i);
                    }
                }
                
                m_AccessorPtr->Expand(ExpandPtr, m_ChunkSizeComplete);
                ::free(ExpandPtr);
                return ::Wiz::Bool::True;
            }

            ::Wiz::Void::Type Type::ReadInfoAndAdvance()
            {
                m_AccessorPtr->MoveToBegin();

                m_Info.ReadAndAdvance(m_AccessorPtr);

                m_Endian =
                    m_Info.m_Endian == ::Wiz::Endian::Storage::LittleEndian?
                    ::Wiz::Endian::eLittle : ::Wiz::Endian::eBig;

            }

            ::Wiz::Void::Type Type::WriteInfoAndAdvance()
            {
                m_AccessorPtr->MoveToBegin();

                m_Info.m_Endian =
                    m_Endian == ::Wiz::Endian::eLittle ? 
                    ::Wiz::Endian::Storage::LittleEndian : ::Wiz::Endian::Storage::BigEndian;
                m_Info.WriteAndAdvance(m_AccessorPtr);
            }
            //////////////////////////////////////////////////////////////////////////

            ::Wiz::Bool::Type Type::ReadDirTree()
            {
                ::Wiz::ByteBuffer::Type OutBuff;
                ::Wiz::VFS::BlockStorage::Param::Type Param;
                Param.m_BlockInfo.m_PreChunkID = m_Info.m_DirTreeChunkID;
                Param.m_BlockInfo.m_PreBlockID = m_Info.m_DirTreeBlockID;
                OutBuff.Resize(m_Info.m_DirTreeSize);
                if (this->Read(OutBuff.GetPtr(), m_Info.m_DirTreeSize, Param))
                {
                    ::Wiz::Archive::Ptr ArchivePtr = ::Wiz::Archive::Create(
                        ::Wiz::Archive::Category::eInput,
                        ::Wiz::Endian::eLittle,
                        OutBuff
                        );
                    m_DirTree.Read(*ArchivePtr);
                    ::Wiz::Archive::Destory(ArchivePtr);
                    return ::Wiz::Bool::True;
                }
                return ::Wiz::Bool::False;
            }
            ::Wiz::Bool::Type Type::WriteDirTree()
            {
                ::Wiz::ByteBuffer::Type NewBuffer;
                {
                    ::Wiz::Archive::Ptr ArchivePtr =
                        ::Wiz::Archive::Create(
                        ::Wiz::Archive::Category::eOutput,
                        m_Endian,
                        NewBuffer
                        );
                    m_DirTree.Write(*ArchivePtr);
                    ::Wiz::Archive::Destory(ArchivePtr);
                }
                ::Wiz::VFS::BlockStorage::Param::Type Param;
                this->Write(NewBuffer.GetPtr(), (::Wiz::Size::Type)NewBuffer.GetElementCount(), Param);
                m_Info.m_DirTreeChunkID = Param.m_CurrChunkID;
                m_Info.m_DirTreeBlockID = Param.m_LastWriteBlockID;
                m_Info.m_DirTreeSize = (::Wiz::Size::Type)NewBuffer.GetElementCount();

                this->WriteInfoAndAdvance();
                return ::Wiz::Bool::True;
            }
            ::Wiz::Bool::Type Type::RemoveDirTree()
            {
                ::Wiz::VFS::BlockStorage::Param::Type Param;
                Param.m_BlockInfo.m_PreChunkID = m_Info.m_DirTreeChunkID;
                Param.m_BlockInfo.m_PreBlockID = m_Info.m_DirTreeBlockID;
                return ::Wiz::Bool::True;
            }
        } /// end of namespace BlockStorage
    } /// end of namespace VFS
} /// end of namespace Wiz


